Added pushbullet agent, refactored JSONPath merging

Moved the JSONPath merging into a concern because it is now used by
two agents, I also added Agent#received_event_without_error? which
is used by the hipchat and pushbullet agents.

Dominik Sander 11 years ago
parent
commit
20ba2be285

+ 20 - 0
app/concerns/json_path_options_overwritable.rb

@@ -0,0 +1,20 @@
1
+module JsonPathOptionsOverwritable
2
+  extend ActiveSupport::Concern
3
+
4
+  private
5
+  def merge_json_path_options(event)
6
+    options.select { |k, v| options_with_path.include? k}.tap do |merged_options|
7
+      options_with_path.each do |a|
8
+        merged_options[a] = select_option(event, a)
9
+      end
10
+    end
11
+  end
12
+
13
+  def select_option(event, a)
14
+    if options[a.to_s + '_path'].present?
15
+      Utils.value_at(event.payload, options[a.to_s + '_path'])
16
+    else
17
+      options[a]
18
+    end
19
+  end
20
+end

+ 4 - 0
app/models/agent.rb

@@ -91,6 +91,10 @@ class Agent < ActiveRecord::Base
91 91
     last_event_at && last_error_log_at && last_error_log_at > (last_event_at - 2.minutes)
92 92
   end
93 93
 
94
+  def received_event_without_error?
95
+    (last_receive_at.present? && last_error_log_at.blank?) || (last_receive_at.present? && last_error_log_at.present? && last_receive_at > last_error_log_at)
96
+  end
97
+
94 98
   def create_event(attrs)
95 99
     if can_create_events?
96 100
       events.create!({ 

+ 3 - 17
app/models/agents/hipchat_agent.rb

@@ -1,5 +1,7 @@
1 1
 module Agents
2 2
   class HipchatAgent < Agent
3
+    include JsonPathOptionsOverwritable
4
+
3 5
     cannot_be_scheduled!
4 6
     cannot_create_events!
5 7
 
@@ -47,30 +49,14 @@ module Agents
47 49
     def receive(incoming_events)
48 50
       client = HipChat::Client.new(options[:auth_token])
49 51
       incoming_events.each do |event|
50
-        mo = merge_options event
52
+        mo = merge_json_path_options event
51 53
         client[mo[:room_name]].send(mo[:username], mo[:message], :notify => mo[:notify].to_s == 'true' ? 1 : 0, :color => mo[:color])
52 54
       end
53 55
     end
54 56
 
55 57
     private
56
-    def select_option(event, a)
57
-      if options[a.to_s + '_path'].present?
58
-        Utils.value_at(event.payload, options[a.to_s + '_path'])
59
-      else
60
-        options[a]
61
-      end
62
-    end
63
-
64 58
     def options_with_path
65 59
       [:room_name, :username, :message, :notify, :color]
66 60
     end
67
-
68
-    def merge_options event
69
-      options.select { |k, v| options_with_path.include? k}.tap do |merged_options|
70
-        options_with_path.each do |a|
71
-          merged_options[a] = select_option(event, a)
72
-        end
73
-      end
74
-    end
75 61
   end
76 62
 end

+ 68 - 0
app/models/agents/pushbullet_agent.rb

@@ -0,0 +1,68 @@
1
+module Agents
2
+  class PushbulletAgent < Agent
3
+    include JsonPathOptionsOverwritable
4
+
5
+    cannot_be_scheduled!
6
+    cannot_create_events!
7
+
8
+    description <<-MD
9
+      The Pushbullet agent sends pushes to a pushbullet device
10
+
11
+      To authenticate you need to set the `api_key`, you can find yours at your account page:
12
+
13
+      `https://www.pushbullet.com/account`
14
+
15
+      Currently you need to get a the device identification manually:
16
+
17
+      `curl -u <your api key here>: https://api.pushbullet.com/api/devices`
18
+
19
+      Put one of the retured `iden` strings into the `device_id` field.
20
+
21
+      You can provide a `title` and a `body`.
22
+
23
+      If you want to specify `title` or `body` per event, you can provide a [JSONPath](http://goessner.net/articles/JsonPath/) for each of them.
24
+    MD
25
+
26
+    def default_options
27
+      {
28
+        'api_key' => '',
29
+        'device_id' => '',
30
+        'title' => "Hello from Huginn!",
31
+        'title_path' => '',
32
+        'body' => '',
33
+        'body_path' => '',
34
+      }
35
+    end
36
+
37
+    def validate_options
38
+      errors.add(:base, "you need to specify a pushbullet api_key") unless options['api_key'].present?
39
+      errors.add(:base, "you need to specify a device_id") if options['device_id'].blank?
40
+    end
41
+
42
+    def working?
43
+      received_event_without_error?
44
+    end
45
+
46
+    def receive(incoming_events)
47
+      incoming_events.each do |event|
48
+        response = HTTParty.post "https://api.pushbullet.com/api/pushes", query_options(event)
49
+        log(response.body) if response.body.include? 'error'
50
+      end
51
+      save!
52
+    end
53
+
54
+    private
55
+    def query_options(event)
56
+      mo = merge_json_path_options event
57
+      basic_options.deep_merge(:body => {:title => mo[:title], :body => mo[:body]})
58
+    end
59
+
60
+    def basic_options
61
+      {:basic_auth => {:username =>options[:api_key], :password=>''}, :body => {:device_iden => options[:device_id], :type => 'note'}}
62
+    end
63
+
64
+    def options_with_path
65
+      [:title, :body]
66
+    end
67
+  end
68
+end

+ 24 - 0
spec/models/agent_spec.rb

@@ -591,6 +591,30 @@ describe Agent do
591 591
     end
592 592
   end
593 593
 
594
+  describe "received_event_without_error?" do
595
+    before do
596
+      @agent = Agent.new
597
+    end
598
+
599
+    it "should return false until the first event was received" do
600
+      @agent.received_event_without_error?.should == false
601
+      @agent.last_receive_at = Time.now
602
+      @agent.received_event_without_error?.should == true
603
+    end
604
+
605
+    it "should return false when the last error occured after the last received event" do
606
+      @agent.last_receive_at = Time.now - 1.minute
607
+      @agent.last_error_log_at = Time.now
608
+      @agent.received_event_without_error?.should == false
609
+    end
610
+
611
+    it "should return true when the last received event occured after the last error" do
612
+      @agent.last_receive_at = Time.now
613
+      @agent.last_error_log_at = Time.now - 1.minute
614
+      @agent.received_event_without_error?.should == true
615
+    end
616
+  end
617
+
594 618
   describe "scopes" do
595 619
     describe "of_type" do
596 620
       it "should accept classes" do

+ 3 - 23
spec/models/agents/hipchat_agent_spec.rb

@@ -1,6 +1,9 @@
1 1
 require 'spec_helper'
2
+require 'models/concerns/json_path_options_overwritable'
2 3
 
3 4
 describe Agents::HipchatAgent do
5
+  it_behaves_like 'JsonPathOptionsOverwritable'
6
+
4 7
   before(:each) do
5 8
     @valid_params = {
6 9
                       'auth_token' => 'token',
@@ -49,29 +52,6 @@ describe Agents::HipchatAgent do
49 52
 
50 53
   end
51 54
 
52
-  describe "helpers" do
53
-    describe "select_option" do
54
-      it "should use the room_name_path if specified" do
55
-        @checker.options['room_name_path'] = "$.room_name"
56
-        @checker.send(:select_option, @event, :room_name).should == "test room"
57
-      end
58
-
59
-      it "should use the normal option when the path option is blank" do
60
-        @checker.send(:select_option, @event, :room_name).should == "test"
61
-      end
62
-    end
63
-
64
-    it "should merge all options" do
65
-      @checker.send(:merge_options, @event).deep_symbolize_keys.should == {
66
-        :room_name => "test",
67
-        :username => "Huggin user",
68
-        :message => "Looks like its going to rain",
69
-        :notify => false,
70
-        :color => "yellow"
71
-      }
72
-    end
73
-  end
74
-
75 55
   describe "#receive" do
76 56
     it "send a message to the hipchat" do
77 57
       any_instance_of(HipChat::Room) do |obj|

+ 80 - 0
spec/models/agents/pushbullet_agent_spec.rb

@@ -0,0 +1,80 @@
1
+require 'spec_helper'
2
+require 'models/concerns/json_path_options_overwritable'
3
+
4
+describe Agents::PushbulletAgent do
5
+  it_behaves_like 'JsonPathOptionsOverwritable'
6
+
7
+  before(:each) do
8
+    @valid_params = {
9
+                      'api_key' => 'token',
10
+                      'device_id' => '124',
11
+                      'body_path' => '$.body',
12
+                      'title' => 'hello from huginn'
13
+                    }
14
+
15
+    @checker = Agents::PushbulletAgent.new(:name => "somename", :options => @valid_params)
16
+    @checker.user = users(:jane)
17
+    @checker.save!
18
+
19
+    @event = Event.new
20
+    @event.agent = agents(:bob_weather_agent)
21
+    @event.payload = { :body => 'One two test' }
22
+    @event.save!
23
+  end
24
+
25
+  describe "validating" do
26
+    before do
27
+      @checker.should be_valid
28
+    end
29
+
30
+    it "should require the api_key" do
31
+      @checker.options['api_key'] = nil
32
+      @checker.should_not be_valid
33
+    end
34
+
35
+    it "should require the device_id" do
36
+      @checker.options['device_id'] = nil
37
+      @checker.should_not be_valid
38
+    end
39
+  end
40
+
41
+  describe "helpers" do
42
+    it "it should return the correct basic_options" do
43
+      @checker.send(:basic_options).should == {:basic_auth => {:username =>@checker.options[:api_key], :password=>''},
44
+                                               :body => {:device_iden => @checker.options[:device_id], :type => 'note'}}
45
+    end
46
+
47
+
48
+    it "should return the query_options" do
49
+      @checker.send(:query_options, @event).should == @checker.send(:basic_options).deep_merge({
50
+        :body => {:title => 'hello from huginn', :body => 'One two test'}
51
+      })
52
+    end
53
+  end
54
+
55
+  describe "#receive" do
56
+    it "send a message to the hipchat" do
57
+      stub_request(:post, "https://token:@api.pushbullet.com/api/pushes").
58
+        with(:body => "device_iden=124&type=note&title=hello%20from%20huginn&body=One%20two%20test").
59
+        to_return(:status => 200, :body => "ok", :headers => {})
60
+      dont_allow(@checker).log
61
+      @checker.receive([@event])
62
+    end
63
+
64
+    it "should log resquests which return an error" do
65
+      stub_request(:post, "https://token:@api.pushbullet.com/api/pushes").
66
+        with(:body => "device_iden=124&type=note&title=hello%20from%20huginn&body=One%20two%20test").
67
+        to_return(:status => 200, :body => "error", :headers => {})
68
+      mock(@checker).log("error")
69
+      @checker.receive([@event])
70
+    end
71
+  end
72
+
73
+  describe "#working?" do
74
+    it "should not be working until the first event was received" do
75
+      @checker.should_not be_working
76
+      @checker.last_receive_at = Time.now
77
+      @checker.should be_working
78
+    end
79
+  end
80
+end

+ 31 - 0
spec/models/concerns/json_path_options_overwritable.rb

@@ -0,0 +1,31 @@
1
+require 'spec_helper'
2
+
3
+shared_examples_for 'JsonPathOptionsOverwritable' do
4
+  before(:each) do
5
+    @valid_params = described_class.new.default_options
6
+
7
+    @checker = described_class.new(:name => "somename", :options => @valid_params)
8
+    @checker.user = users(:jane)
9
+
10
+    @event = Event.new
11
+    @event.agent = agents(:bob_weather_agent)
12
+    @event.payload = { :room_name => 'test room', :message => 'Looks like its going to rain', username: "Huggin user"}
13
+    @event.save!
14
+  end
15
+
16
+  describe "select_option" do
17
+    it "should use the room_name_path if specified" do
18
+      @checker.options['room_name_path'] = "$.room_name"
19
+      @checker.send(:select_option, @event, :room_name).should == "test room"
20
+    end
21
+
22
+    it "should use the normal option when the path option is blank" do
23
+      @checker.options['room_name'] = 'test'
24
+      @checker.send(:select_option, @event, :room_name).should == "test"
25
+    end
26
+  end
27
+
28
+  it "should merge all options" do
29
+    @checker.send(:merge_json_path_options, @event).symbolize_keys.keys.should == @checker.send(:options_with_path)
30
+  end
31
+end

+ 7 - 2
spec/spec_helper.rb

@@ -1,8 +1,13 @@
1 1
 # This file is copied to spec/ when you run 'rails generate rspec:install'
2 2
 ENV["RAILS_ENV"] ||= 'test'
3 3
 
4
-require 'coveralls'
5
-Coveralls.wear!('rails')
4
+if ENV['COVERAGE']
5
+  require 'simplecov'
6
+  SimpleCov.start 'rails'
7
+else
8
+  require 'coveralls'
9
+  Coveralls.wear!('rails')
10
+end
6 11
 
7 12
 require File.expand_path("../../config/environment", __FILE__)
8 13
 require 'rspec/rails'